using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.EntityClient;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using LinqToEdmx.Map;
using Magiq.Databases;
using Magiq.Databases.Delete;
using Magiq.Databases.Insert;
using Magiq.Databases.SqlServer;
using Magiq.Databases.Update;
using Magiq.Delete;
using Magiq.Insert;
using Magiq.Providers.Entities.Insert;
using Magiq.Providers.Entities.Query;
using Magiq.Providers.Entities.Support;
using Magiq.Query;
using Magiq.Support;
using Magiq.Update;
using MirrorMirror;

namespace Magiq.Providers.Entities
{
    public class LinqToEntitiesAdapter : IOrmAdapter
    {
        private readonly ObjectContext objectContext;

        public IDialect Dialect { get { return SqlServerDialect.Instance; } }

        public IQueryable<TChild> GetQueryableFrom<T, TChild>(QueryInfo<T, TChild> queryInfo) where T : class
        {
            return queryInfo.Item.IsEntityFrameworkProxy() ? new MagiqToEntitiesCollectionQueryable<T,TChild>(queryInfo, objectContext) : null;
        }

        public IQueryable<TSource> GetSourceQueryableFrom<TSource, TNew>(InsertSource<TNew> source)
        {
            var objectSet = (IQueryable)
               typeof(ObjectContext).GetMethod("CreateObjectSet", new Type[0]).MakeGenericMethod(
                  source.SourceCollectionItemType).Invoke(objectContext, null);

            return (IQueryable<TSource>)source.ApplyQueryTo(objectSet);
        }

        public IEnumerable<string> GetOrderedValuesFrom(SelectStatementInformation select, IQueryable selectQuery)
        {
            var properties = selectQuery.Get("_state")
                                              .Invoke("GetExecutionPlan", NullValue.For<MergeOption>())
                                              .Get<IEnumerable>("CommandDefinition._columnMapGenerator._columnMap.Element.Properties");

            foreach (var property in properties)
            {
                var column = property.Get<int>("ColumnPos");
                yield return select[column];
            }

        }

        public LinqToEntitiesAdapter(ObjectContext objectContext)
        {
            this.objectContext = objectContext;
        }

        #region IDatabaseAdapter Members

        public IDbCommand GetSelectCommand(IQueryable queryable)
        {
            var objectQuery = (ObjectQuery)queryable;
            var command = ((EntityConnection)objectContext.Connection).StoreConnection.CreateCommand();
            command.CommandType = CommandType.Text;
            command.CommandText = objectQuery.ToTraceString();
            //command.Prepare();
            foreach (var parameter in objectQuery.Parameters)
            {
                var dbParameter = command.CreateParameter();
                dbParameter.ParameterName = parameter.Name;
                dbParameter.Value = parameter.Value;
                command.Parameters.Add(dbParameter);
            }

            return command;
        }

        ITypeMapping IOrmAdapter.GetMappingOf(Type type)
        {
            return GetMappingOf(type);
        }

        private EntitiesTypeMapping GetMappingOf(Type type)
        {
            return new EntitiesTypeMapping(type, EdmxProvder.From(type, objectContext));
        }

        public IEnumerable<DatabaseDelete<T>> BuildDatabaseDeletes<T>(IDelete<T> delete) where T : class
        {
            yield return new DatabaseDelete<T>(this)
                             {
                                 Source = delete.Source
                             };
        }

        public IEnumerable<DatabaseUpdate<T>> BuildDatabaseUpdates<T>(IUpdate<T> update) where T : class
        {
            var emulator = new MultipleTableUpdateEmulation<T>(update, this);
            return emulator.GetTableUpdates();
        }

        public IEnumerable<DatabaseInsert<TSource>> BuildDatabaseInserts<TSource, TDestination, TNew>(
            IInsert<TSource, TDestination, TNew> insert)
        {
            var mapping = GetMappingOf(typeof (TNew));
            var newExpression = (MemberInitExpression) insert.NewExpression.Body;

            var databaseInsert = new DatabaseInsert<TSource>
                                     {
                                         OrmAdapter = this,
                                         DestinationTypeMapping = mapping,
                                         Source = GetSourceQueryableFrom<TSource,TNew>(insert.Source)
                                     };

            foreach (MemberAssignment binding in newExpression.Bindings)
            {
                var parameter = binding.Expression.IsConstant()
                                    ? Expression.Parameter(newExpression.Type, "x")
                                    : binding.Expression.GetParameter();

                var valueExpression = Expression.Lambda(binding.Expression, parameter);
                var property = (PropertyInfo) binding.Member;

                var assignment = new InsertColumnsAssignment
                                     {
                                         Columns = mapping.ColumnsFor(property.Name),
                                         PropertyName = property.Name,
                                         PropertyType = property.PropertyType,
                                         ValueExpression = valueExpression,
                                         ValueType = property.PropertyType
                                     };

                databaseInsert.ColumnAssignments.Add(assignment);
            }

            //conditions

            var conditions = mapping.Conditions;
                
            foreach (var condition in conditions)
            {
                var valueExpression = Expression.Lambda(Expression.Constant(condition.Value),
                                                            Expression.Parameter(typeof(TNew), "x"));

                databaseInsert.ColumnAssignments.Add(new InsertColumnsAssignment
                                                         {
                                                             Columns = new[]{condition.ColumnName},
                                                             ValueExpression = valueExpression,
                                                             ValueType = valueExpression.Body.Type
                                                         });
            }

            //Collections
            
            var collection = insert.Destination as IMagiqToEntitiesCollectionQueryable;
            
            if (collection != null)
            {
                var member = collection.Member;
                
                var parentMapping = GetMappingOf(collection.ParentType);

                var associationThisKeys = collection.ParentKeyMembers;

                foreach (var keyMember in associationThisKeys)
                {
                    var parentKeyValue = keyMember.Value;
                    var valueExpression = Expression.Lambda(Expression.Constant(parentKeyValue),
                                                            Expression.Parameter(typeof (TNew), "x"));

                    var parentType = collection.ParentType.Name;

                    var navigation = parentMapping.EntityType.NavigationProperties.SingleOrDefault(x => x.Name == member.Name);

                    var associationMapping = parentMapping.Edmx.GetItems<AssociationSetMapping>()
                        .Single(x => x.TypeName == navigation.Relationship);

                    var columns = associationMapping.EndProperties.Single(x => x.Name == parentType)
                        .ScalarProperties.Select(x => x.ColumnName);

                    databaseInsert.ColumnAssignments.Add(new InsertColumnsAssignment
                                                             {
                                                                 Columns = columns,
                                                                 PropertyName = null,//childKeyProperty.Name,
                                                                 PropertyType = null,//childKeyProperty.PropertyType,
                                                                 ValueExpression = valueExpression,
                                                                 ValueType = valueExpression.Body.Type
                                                             });
                }
            }

            yield return databaseInsert;
        }

        public IInsertDestination<TNew> GetInsertDestination<TSource, TDestination, TNew>(
            IInsert<TSource, TDestination, TNew> insert)
            where TDestination : class
            where TNew : class, TDestination, new()
        {
            var destination = insert.Destination;

            if (destination is ObjectSet<TDestination>)
                return new InsertObjectSet<TDestination, TNew>((ObjectSet<TDestination>) destination, this);

            var collection = destination as IMagiqToEntitiesCollectionQueryable;

            return collection != null ? new CollectionInsertDestination<TDestination, TNew>(collection) : null;
        }
        
        public IDbConnection GetUnderlyingDatabaseConnection()
        {
            return ((EntityConnection) objectContext.Connection).StoreConnection;
        }

        #endregion
    }
}